/* 2014/10/24 */
package com.cosmos.quartz.control.xml;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import com.cosmos.quartz.control.ControlRequest;
import com.cosmos.quartz.control.ControlResponse;
import com.cosmos.quartz.control.QuartzController;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class XmlMessageQuartzControlJob implements Job {

	private static final Logger log = Logger.getLogger(XmlMessageQuartzControlJob.class.getSimpleName());
	
	private static final DateFormat requestFileDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault());
	private static final DateFormat responseFileDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault());
	
	private static final XStream xStream;
	
	private static final String requestDirPath;	
	private static final String requestIncomingDirPath;
	private static final String requestArchiveDirPath;
	private static final String requestErrorDirPath;
	private static final String responseDirPath;
	
	static {
		xStream = new XStream(new DomDriver());
		xStream.processAnnotations(XmlControlRequest.class);
		xStream.processAnnotations(XmlControlResponse.class);		
		
		String packageName = XmlMessageQuartzControlJob.class.getPackage().getName();
		
		String baseName = System.getProperty(packageName + ".properties");
		if (baseName == null) {
			baseName = "control";
		}	
		ResourceBundle bundle = null;
		try {						
			bundle = ResourceBundle.getBundle(baseName);			
		} catch (MissingResourceException ex) {
			log.error("Get resouce bundle \"" + baseName + "\" failed.", ex);
		}		

		if (bundle != null) {
			requestDirPath = getStringFromResourceBundle(bundle, "AgentControlRequestDir", ".");
			responseDirPath = getStringFromResourceBundle(bundle, "AgentControlResponseDir", ".");
		} else {
			requestDirPath = getStringFromSystemProperty(packageName + ".requestDir", ".");
			responseDirPath = getStringFromSystemProperty(packageName + ".responseDir", ".");
		}
		
		requestIncomingDirPath = requestDirPath + "\\incoming";
		requestArchiveDirPath = requestDirPath + "\\archive";
		requestErrorDirPath = requestDirPath + "\\error";
		
	}
	
	private static String getStringFromResourceBundle(ResourceBundle bundle, String key, String defaultValue) {
		String value = defaultValue;
		if (bundle != null) {
			try {
				value = bundle.getString(key);
				log.info("Get string form resource bundle. Key: " + key + ", value: " + value);
			} catch (MissingResourceException ex) {
				log.error("Get string from resource bundle failed. Key: " + key, ex);
				log.info("Use default value: " + defaultValue);
			}	
		}
		return value;
	}
	
	private static String getStringFromSystemProperty(String key, String defaultValue) {
		String value = System.getProperty(key);
		if (value != null) {
			log.info("Get string from system property. Key: " + key + ", value: " + value);
		} else {
			log.error("Get string from system property failed. Key: " + key);
			
			value = defaultValue;
			log.info("Use default value: " + defaultValue);
		}
		return value;
	}
	
	public void execute(JobExecutionContext context) throws JobExecutionException {
		
		List<XmlControlRequest> requests = receiveRequests();
		if (requests == null || requests.size() == 0) {
			return;
		}
		log.info("Requests are received.");
		
		QuartzController controller = new QuartzController();
		List<XmlControlResponse> responses = new ArrayList<XmlControlResponse>(requests.size());
		for (XmlControlRequest request : requests) {
			ControlRequest baseRequest = request.toBaseRequest();
			ControlResponse baseResponse = controller.process(baseRequest);			
			XmlControlResponse response = new XmlControlResponse(baseResponse);
			
			if (response.isSucceed()) {
				acceptRequest(request);
			} else {
				rejectRequest(request);
			}
			responses.add(response);
		}
		
		sendResponses(responses);
		log.info("Responses are sent.");
	}
	
	protected List<XmlControlRequest> receiveRequests() {
		File requestDir = new File(requestIncomingDirPath);
		if (!requestDir.exists()) {
			log.error(String.format("Request file directory \"%s\" is not existed.", requestIncomingDirPath));
			return null;
		}
		
		File[] requestFiles = requestDir.listFiles(new FilenameFilter() {
			
			public boolean accept(File dir, String name) {
				// Ignore files which are not XML.
				if (!name.endsWith(".xml") && !name.endsWith(".XML")) {
					return false;
				}
				
				return true;
			}
		});
		
		List<XmlControlRequest> requests = new ArrayList<XmlControlRequest>();
		for (File requestFile : requestFiles) {
			Reader reader = null;
			InputStream input = null;
			try {
				input = new FileInputStream(requestFile);
				reader = new InputStreamReader(input, "UTF-8");
				XmlControlRequest request = (XmlControlRequest)xStream.fromXML(requestFile);
				request.setFilePath(requestFile.getAbsolutePath());
				requests.add(request);
			} catch (Exception ex) {
				log.error("Read request file failed.", ex);
			} finally {
				close(reader);
				close(input);
			}
		}
		
		return requests;
	}
	
	protected void acceptRequest(XmlControlRequest request) {
		try {
			File requestFile = new File(request.getFilePath());
			Date now = new Date();
			File processedFile = new File(requestArchiveDirPath, requestFileDateFormat.format(now) + requestFile.getName());
			log.info(String.format("Move %s to %s.", requestFile.getAbsolutePath(), processedFile.getAbsolutePath()));
			boolean success = requestFile.renameTo(processedFile);
			if (success) {
				log.info("Move request file succeed.");
			} else {
				log.info("Move request file failed.");
			}
		} catch (Exception ex) {
			log.error("Move request file failed.", ex);
		}
	}
	
	protected void rejectRequest(XmlControlRequest request) {
		try {
			File requestFile = new File((String)request.getFilePath());
			Date now = new Date();
			File processedFile = new File(requestErrorDirPath, requestFileDateFormat.format(now) + requestFile.getName());
			log.info(String.format("Move %s to %s.", requestFile.getAbsolutePath(), processedFile.getAbsolutePath()));
			boolean success = requestFile.renameTo(processedFile);
			if (success) {
				log.info("Move request file succeed.");
			} else {
				log.info("Move request file failed.");
			}
		} catch (Exception ex) {
			log.error("Move request file failed.", ex);
		}
	}

	protected void sendResponses(List<XmlControlResponse> responses) {
		if (responses == null) {
			return;
		}
		
		File responseDir = new File(responseDirPath);
		if (!responseDir.exists()) {
			log.error(String.format("Response file directory \"%s\" is not existed.", responseDirPath));
			return;
		}
		
		for (XmlControlResponse response : responses) {
			Date now = new Date();
			String fileName = responseFileDateFormat.format(now) + ".xml";
			File responseFile = new File(responseDirPath, fileName);
			Writer writer = null;
			OutputStream output = null;
			try {
				output = new FileOutputStream(responseFile);
				writer = new OutputStreamWriter(output, "UTF-8");
				xStream.toXML(response, writer);
			} catch (Exception ex) {
				log.error("Write response file failed.", ex);
			} finally {
				close(writer);
				close(output);
			}
		}
	}

	private void close(Closeable resource) {
		if (resource != null) {
			try {
				resource.close();
			} catch (IOException ex) {
				log.error("Close resource failed.", ex);
			}	
		}
	}
	
}
